package com.jielihaofeng.nio.mesageboundary;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

/**
 * @author Johnnie Wind
 * @description 演示半包常见服务端代码
 * @date 2021/10/27 23:28
 */
public class Server {

    private static void split(ByteBuffer source) {
        source.flip();
        for (int i = 0; i < source.limit(); i++) {
            // 注意：\n 也是一个字节
            if (source.get(i) == '\n') {
                int length = i + 1 - source.position();
                // 把完整消息存入新的ByteBuffer
                ByteBuffer target = ByteBuffer.allocate(length);
                // 从 source 读，向 target 写
                for (int j = 0; j < length; j++) {
                    target.put(source.get());
                }
//                debugAll(target);
                target.flip();
                System.out.print(Charset.defaultCharset().decode(target));
            }
        }
        source.compact();
    }

    public static void main(String[] args) throws IOException {

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8080));
        serverSocketChannel.configureBlocking(false);

        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select();
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                iterator.remove();

                if (selectionKey.isAcceptable()) {
                    // 接收客户端连接事件
                    ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel sc = channel.accept();
                    sc.configureBlocking(false);
                    ByteBuffer buffer = ByteBuffer.allocate(4); // 作为附件关联到SelectionKey上
                    SelectionKey scKey = sc.register(selector, 0, buffer);
                    scKey.interestOps(SelectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    try {
                        SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
//                        ByteBuffer buffer = ByteBuffer.allocate(4);
                        // 获取ByteBuffer附件
                        ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
                        int read = socketChannel.read(buffer);
                        if (read == -1) {
                            selectionKey.cancel();
                        } else {
//                            buffer.flip();
//                            System.out.println(Charset.defaultCharset().decode(buffer));
                            split(buffer);
                            // 判断是否需要扩容，position = limit
                            if (buffer.position() == buffer.limit()) {
                                // 扩容原来大小的两倍
                                ByteBuffer newBuffer = ByteBuffer.allocate(buffer.capacity() * 2);
                                // 切换读模式
                                buffer.flip();
                                // 把内容存入新的ByteBuffer
                                newBuffer.put(buffer);
                                // 扩容后的替换旧buffer
                                selectionKey.attach(newBuffer);
                            }
                        }
                    } catch (IOException e) {
                        // 处理客户端断开异常
                        e.printStackTrace();
                        // 取消事件
                        selectionKey.cancel();
                    }
                }
            }
        }
    }
}

// 1.约定最大长度。
// 客户端内容不够就补齐，服务端分配最大容量。
// 简单，但是会造成空间浪费，网络带宽受到影响。实际用的很少。

// 2.用特殊分隔符分割消息，如"\n"
// 服务端根据分隔符创建新的ByteBuffer接收读取消息。
// 存在扩容问题
// 效率低

// 3.把消息分成两个部分，第一部分是固定大小，存储了后面内容的长度（LTV）
// 服务器，第一步读取消息的第一部分，获得长度，分配该长度的ByteBuffer来读取
// 比较常用，例如HTTP
